Skip to content

feat: rebuild the Cursor adapter for OpenCode V2 - #35

Merged
sdziupin merged 2 commits into
otto-assistant:betafrom
CasualDeveloper:feat/opencode-v2-host-owned-cursor
Sep 24, 2026
Merged

sdziupin merged 2 commits into
otto-assistant:betafrom
CasualDeveloper:feat/opencode-v2-host-owned-cursor

Conversation

@CasualDeveloper

@CasualDeveloper CasualDeveloper commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Hey, @makeittech - was excited to try out Astra and I think it did a great job with our plugin!

Issue for this PR

Refs #31. Please keep it open through publication and release checks.

This PR supersedes #34 and includes its release-flow documentation and constants-test explanation. Please review and merge this PR directly.

Type of change

  • New feature
  • Refactor / code improvement

What does this PR do?

I rebuilt the Cursor adapter for OpenCode V2 and removed the old proxy and compatibility code. It keeps private models and exact variants while OpenCode manages history, tools, permissions, and compaction.

Each new Cursor Run is built from OpenCode's messages and tool results, including images and reasoning data. This lets the host recover after a lost connection or restart without a second conversation store.

Each step derives OpenCode input from Cursor's checkpoint without counting generated output twice. A matching terminal checkpoint uses the reported input count; post-generation checkpoints subtract that step's streamed output, while pre-generation checkpoints remain input occupancy. Exact Run counters, including cache reads and writes, stay in provider metadata. Cursor gives no per-call cache split, so OpenCode's cost estimate is an upper bound on long tool loops.

Cursor is offered only its MCP tool family: the same x-cursor-agent-allowed-tools list the official SDK sends for tools: ["mcp"]. Models see OpenCode's tool names and schemas instead of a second set of Cursor-native tools. The list includes get_mcp_tools_tool_call, so Cursor still starts normally; allowing only mcp_tool_call broke bootstrap. Native workspace requests that still arrive (shellStreamArgs, shellArgs, grepArgs, readArgs, writeArgs, lsArgs) map to OpenCode host tools, empty-pattern grep calls route to glob, and tool result JSON envelopes are unwrapped.

Every Cursor exec frame and interaction query now gets a reply:

  • Native shell streams finish with shellResult and streamClose. Without them, Cursor kept the turn pending after every shell call, so each continuation stalled until the watchdog fired. This is why long sessions looked stuck.
  • Frames the adapter can't serve fail in band with throw and streamClose instead of leaving Cursor waiting.
  • Cursor-hosted web search, Exa, web fetch, question, and mode-switch prompts are declined so the model uses OpenCode's tools. Plan creation gets an error, and VM setup fails the Run.
  • Native StrReplace is declined. Cursor applies it to the read result as raw bytes, so it would write OpenCode's formatted read output back into the file.

A resumed Run waits up to 180 seconds for first output, like a fresh one. A rebuilt Run tells the model that the preceding tool results answer its own calls, instead of an opener that read like an interruption. The host-contract rule names OpenCode's rules as the application's system prompt rather than the user's.

I updated the adapter after OpenCode removed context.catalog. It now uses context.provider.transform() and reload(), refreshes models on credential.switched, and preserves failed-tool outcomes in structured compaction requests. Dependencies and the test host are pinned to released @opencode/* 2.0.6. Older hosts with the catalog API are no longer supported.

Grok opaque-reasoning checkpoints no longer fail a completed turn when a root has no unique assistant anchor. Cursor usable-model IDs group into families: none is the non-thinking SKU, and thinking SKUs become low/medium/high/xhigh/max.

The OpenCode id stays the stable catalog name, such as grok-4.6. Each variant still sends Cursor's exact wire id, such as cursor-grok-4.6-xhigh. All non-duplicate models from AvailableModels are published so newly released and parameterized models (such as Grok 4.7 and Grok 4.7 500K) appear in OpenCode's picker. If discovery is incomplete, the usable subset remains available while the adapter retries with bounded backoff; it does not cache that subset as complete. Completed catalogs are credential-scoped, and invalidated in-flight results cannot replace a new connection's catalog. If OpenCode has loaded a models.dev Cursor row, I copy that row's limits and price. I do not copy lab caps, and I do not replace the live catalog with a static list.

I also fixed model grouping against Cursor's real parameter spellings: any effort parameter (including reasoning_effort and minimal) groups into variants on one model, and Claude's thinking flag stays on that same model (none is non-thinking, while effort variants keep thinking enabled) instead of creating separate models. Connect error message details are also preserved in the transport.

How did you verify your code works?

The amended head is 4932ff9. npm run verify passes locally with 95 tests, the pinned OpenCode host acceptance, package validation, and loader checks. The prior head passed CI; the checks on this head are tracked in the PR.

Bounded live checks confirmed Composer and Grok host-tool calls, and a previously incomplete model catalog later published its full inventory. A prior tool-less turn that claimed tools were unavailable was not reproduced, so I am not claiming its precise cause is fixed. Instruction priority on Opus and with images, opaque-reasoning replay, and continued work after automatic compaction still need live verification. The acceptance report records the other limits.

Checklist

  • I have tested my changes locally.
  • I have not included unrelated changes in this PR.

@CasualDeveloper
CasualDeveloper marked this pull request as draft September 10, 2026 13:03
@CasualDeveloper
CasualDeveloper force-pushed the feat/opencode-v2-host-owned-cursor branch from 4faffe5 to 341c781 Compare September 10, 2026 18:11
@CasualDeveloper CasualDeveloper changed the title feat: rebuild Cursor V2 adapter around host-owned history feat: replace beta with a V2-only host-owned Cursor adapter Sep 10, 2026
@CasualDeveloper
CasualDeveloper marked this pull request as ready for review September 10, 2026 18:17
@CasualDeveloper CasualDeveloper changed the title feat: replace beta with a V2-only host-owned Cursor adapter feat: rebuild the Cursor adapter for OpenCode V2 Sep 10, 2026
@CasualDeveloper
CasualDeveloper force-pushed the feat/opencode-v2-host-owned-cursor branch from 341c781 to 3670345 Compare September 16, 2026 05:07
@CasualDeveloper
CasualDeveloper force-pushed the feat/opencode-v2-host-owned-cursor branch 12 times, most recently from b99cfe8 to 8ea167b Compare September 23, 2026 08:40
Reconstruct roles, tool outcomes, images, and reasoning from OpenCode's
history. Preserve exact private-model selections while OpenCode owns
tools, permissions, steering, persistence, and compaction. Remove the old
proxy and compatibility implementation from the V2 channel.

Use bounded per-Run HTTP/2 workers, validated Connect completion, and
authoritative host tool handoffs. Preserve signed and opaque reasoning
through restart.

Report each host step's usage from Cursor's checkpoint without counting
output twice: use matching terminal input, subtract step output from
post-generation checkpoints, and preserve pre-generation occupancy. Tool
steps previously reported no usage, while Run totals could exceed the
window. Keep exact terminal counters in provider metadata.

Answer every Cursor exec frame and interaction query. Complete native
shell streams with shellResult and streamClose; without them Cursor kept
the turn pending after every shell call, so each resumed Run stalled
until the watchdog fired. Fail unserved frames in band, decline
Cursor-hosted web, question, and mode-switch gates, and decline native
StrReplace, which would write formatted host reads back into files.

Offer Cursor only its MCP tool family, the allowlist the official SDK
sends for tools: ["mcp"]. It keeps get_mcp_tools_tool_call, so Cursor
starts normally, and models see OpenCode's tool names and schemas. Map
native workspace requests that still arrive to host tools, route
empty-pattern grep calls to glob, and unwrap tool result JSON envelopes.

Wait up to 180 seconds for a resumed Run's first output, and open
rebuilt Runs by saying the preceding tool results answer the model's own
calls. Name host rules as the application's system prompt: in live
checks, Composer, Grok 4.7, and Auto followed a project instruction over
a conflicting user request in 18 of 18 trials, up from 6 of 9.

Target released @opencode/plugin, @opencode/schema, and @opencode/cli
2.0.6. Replace the removed catalog API with provider source registration
and reload. Align the host fixture with the 2.0.6 permission and form
APIs, and accept npm 12 pack output in the package check.

Match opaque checkpoint roots to visible text and tools even when Cursor
also streamed thinking. Omit unmatched extra roots instead of failing an
already-streamed answer.

Treat Cursor reasoning-effort as a variant parameter, recognizing any
effort-valued parameter including reasoning_effort and minimal. Keep
Claude thinking on that same model: none is non-thinking, and thinking
SKUs become low/medium/high/xhigh/max. Prefer default, then none, low,
medium, high, xhigh, max. Preserve Connect error message details in the
transport.

Publish all non-duplicate models from AvailableModels so newly released
and parameterized models (including Grok 4.7 and Grok 4.7 500K) appear
in OpenCode's picker, sending exact usable wire IDs on each variant. Use
a models.dev Cursor row for limits and price when OpenCode has one; do
not copy lab caps. Keep partial discovery usable without caching it as
complete: retry with bounded backoff, key completed catalogs to the
credential, and discard invalidated in-flight results.

Verified with npm run verify (95 tests) against the pinned 2.0.6 host
and bounded live host-tool checks with Composer and Grok. Earlier Auto
checks remain in the acceptance record; one tool-less turn remains
unexplained. Opt-in protocol traces are bounded and omit content.

Refs otto-assistant#31
@CasualDeveloper
CasualDeveloper force-pushed the feat/opencode-v2-host-owned-cursor branch from 8ea167b to 4932ff9 Compare September 23, 2026 22:32

Copy link
Copy Markdown
Contributor

This turned out really great — thank you for all the work you put into it. I really appreciate how thoroughly you handled the tricky edge cases and kept the remaining limitations clear.

I’ve reviewed the latest 4932ff9 changes, and this is a huge step forward. Really nice work on the V2 transition — we’re merging this into beta now. 🚀

@sdziupin
sdziupin merged commit 64d0a52 into otto-assistant:beta Sep 24, 2026
1 check passed
@CasualDeveloper
CasualDeveloper deleted the feat/opencode-v2-host-owned-cursor branch September 25, 2026 06:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants